//
//  NetEngine.m
//  testpb
//
//  Created by zhangjimin on 15/7/26.
//  Copyright (c) 2015年 zhangjimin. All rights reserved.
//
//#undef TYPE_BOOL

#import "hb.pb.h"
#import "head.pb.h"
#import "account.pb.h"
#import "NetEngine.h"

#import "GCDAsyncSocket.h"
#import "Request.h"
#import "AESCrypt.h"
#import "queueLogic.h"

//port
#define HOST @"HOST"
#define PORT @"PORT"

//
#define READ_TIME_OUT -1
#define WRITE_TIME_OUT 30
#define TAG_LENGTH_HEADER 0
#define TAG_RESPONSE_BODY 1


@interface NetEngine()<GCDAsyncSocketDelegate>
{
    GCDAsyncSocket *gcd_socket;
    NSMutableData *lenBuf;
    NSMutableDictionary *sendedOutReqsDic;
    NSMutableDictionary *pendingReqs;
    BOOL isConnecting;
    BOOL isEnable;
}

@end

@implementation NetEngine

//单例
static NetEngine* _instance = nil;
+(instancetype) shareInstance
{
    static dispatch_once_t onceToken ;
    dispatch_once(&onceToken, ^{
        _instance = [[super allocWithZone:NULL] init] ;
    });
    
    return _instance ;
}
+(id) allocWithZone:(struct _NSZone *)zone
{
    return [NetEngine shareInstance] ;
}


-(id)init
{
    self = [super init];
    if (self) {
        isEnable = false;
        sendedOutReqsDic = [[NSMutableDictionary alloc] init];
        pendingReqs = [[NSMutableDictionary alloc] init];
        gcd_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:[[QueueLogic shareInstance]getWorkQueue]];
        lenBuf = [[NSMutableData alloc] initWithCapacity:4];
        isConnecting = FALSE;
        if (![self hostStr]) {
            [self setHostStr:@"180.150.186.187"];
        }
        if (![self portStr]) {
            [self setPortStr:@"11000"];
        }
    }
   
    return self;
}

-(void) enable {
    isEnable = true;
    [self checkSocket];
}

-(void) disable {
    isEnable = false;
    [sendedOutReqsDic removeAllObjects];
    [pendingReqs removeAllObjects];
    [gcd_socket disconnect];
}



#pragma mark - send request

static UInt32 CurVer = 1;
- (void)sendRequest:(Request*)request
{
    if (request == nil || request.packet == nil)
    {
        return;
    }
    if (!isEnable) {
        DLog(@"not enbaled");
        return;
    }
    dispatch_async([[QueueLogic shareInstance]getWorkQueue],^ {
        [self sendRequestAsync:request];
    });
}

-(void) sendRequestAsync:(Request *)request {
    [self checkSocket];
    NSString *key = [NSString stringWithFormat:@"%d", request.packet.seq];
    Packet *packet = request.packet;
    DLog(@"send cmd:%@ uin:%lld seq:%ud ebody:%ld body:%@",packet.cmd,packet.uin,packet.seq,packet.eBody.length,request.oriPb.description);
    if (gcd_socket.isConnected) {
        [self realsendRequest:request];
    } else {
        [pendingReqs setObject:request forKey:key];
    }
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 6*1000 * NSEC_PER_MSEC), [[QueueLogic shareInstance]getWorkQueue], ^{
        [self requestTimeOut:request];
    });
}

-(void) realsendRequest:(Request *)request {
    NSString *key = [NSString stringWithFormat:@"%d", request.packet.seq];
    NSData *serial = [request.packet data];
    // totalLen(4) + version(4) + packet
    UInt32 len = (UInt32)serial.length + 4 + 4;
    NSMutableData *data = [[NSMutableData alloc] initWithCapacity:len];
    HTONL(len);
    [data appendBytes:&len length:4];
    UInt32 ver = CurVer;
    NTOHL(ver);
    [data appendBytes:&ver length:4];
    [data appendBytes:serial.bytes length:serial.length];
    [gcd_socket writeData:data withTimeout:WRITE_TIME_OUT tag:request.packet.seq];
    [sendedOutReqsDic setObject:request forKey:key];
}

- (void)requestTimeOut:(Request *)request
{
    NSString *key = [NSString stringWithFormat:@"%d", request.packet.seq];
    Request *req = [sendedOutReqsDic objectForKey:key];
    if (req == nil) {
        req = [pendingReqs objectForKey:key];
    }
    if (req != nil) {
        [sendedOutReqsDic removeObjectForKey:key];
        [pendingReqs removeObjectForKey:key];
        Packet *pre = request.packet;
        PacketBuilder *pb = [[PacketBuilder alloc] init];
        [pb setCmd:pre.cmd];
        [pb setSeq:pre.seq];
        [pb setRet:RetCodeTimeout];
        [pb setUin: pre.uin];
        [pb setImei:pre.imei];
        Packet *result = pb.build;
        if (request.delegate != nil) {
            [request.delegate onResult:result req:request];
        }
    }
}

#pragma mark - socket
- (void)setHostStr:(NSString *)hostStr
{
    SetNSUserDefaults(HOST, hostStr);
}
- (NSString *)hostStr
{
    return GetNSUserDefaults(HOST);
}

- (void)setPortStr:(NSString *)portStr
{
    SetNSUserDefaults(PORT, portStr);
}
- (NSString *)portStr
{
    return GetNSUserDefaults(PORT);
}
- (void)resetHost:(NSString *)hostStr andPort:(NSString *)portStr
{
    [self setHostStr:hostStr];
    [self setPortStr:portStr];
    
    [self heartbeatFail];
    [self checkSocket];
    
}

//连接
- (void)checkSocket
{
    if (!isEnable) {
        DLog(@"not enabled");
        return;
    }
    if ([gcd_socket isConnected]) {
        return;
    } else if (isConnecting){
        DLog(@"Networking - already connecting");
        return;
    } else {
        DLog(@"Networking - start connect :%@ port:%@",[self hostStr],[self portStr]);
        NSError *error = nil;
        if (![gcd_socket connectToHost:[self hostStr] onPort:[[self portStr] intValue] error:&error])
        {
            DLog(@"Networking - Error connecting: %@", error);
        } else {
            isConnecting = true;
        }
    }
}

- (void)netReconnect
{
    NSArray *keyArray = [pendingReqs allKeys];
    if (keyArray.count == 0) {
        return;
    }
    
    keyArray = [keyArray sortedArrayUsingSelector:@selector(compare:)];
    for (NSInteger i = 0; i<keyArray.count; i++) {
        [self sendRequestAsync:pendingReqs[keyArray[i]]];
    }
    [pendingReqs removeAllObjects];
}

- (void)heartbeatFail {
    [gcd_socket disconnect];
}

#pragma mark - GCDAsyncSocketDelegate
//已连接
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
    DLog(@"didConnectToHost");
    isConnecting = FALSE;
    [gcd_socket readDataToLength:4 withTimeout:READ_TIME_OUT buffer:lenBuf bufferOffset:0 tag:TAG_LENGTH_HEADER];
    [self netReconnect];
    if (self.pushDelegate != nil) {
        [self.pushDelegate onConnConnect];
    }
}

//已断开
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
    DLog(@"disconnected");
    isConnecting = FALSE;
    [pendingReqs setValuesForKeysWithDictionary:sendedOutReqsDic];
    [sendedOutReqsDic removeAllObjects];
    DLog(@"pending size:%lu",(unsigned long)pendingReqs.count)
    // 把此时未完成的socket保存下来
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*1000 * NSEC_PER_MSEC), dispatch_get_main_queue(),
    ^{
        [self checkSocket];
    });
    if (self.pushDelegate != nil) {
        [self.pushDelegate onConnDisconnect];
    }
}

- (void) clearReqs {
    if (pendingReqs) {
        [pendingReqs removeAllObjects];
        [sendedOutReqsDic removeAllObjects];
    }
}

//收到data后的call back
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    if (tag == TAG_LENGTH_HEADER) {
        UInt32 len = *(UInt32 *)[data bytes];
        NTOHL(len);
        len = len - 4;
        [gcd_socket readDataToLength:len withTimeout:READ_TIME_OUT tag:TAG_RESPONSE_BODY];
    }
    else if (tag == TAG_RESPONSE_BODY)
    {
        UInt32 ver = *(UInt32 *)[data bytes];
        NTOHL(ver);
        NSData *pbData = [data subdataWithRange:NSMakeRange(4, data.length-4)];
        Packet *packet = [Packet parseFromData:pbData];
        DLog(@"recv cmd:%@ ret:%d uin:%lld seq:%ud ebody:%ld error:%@",packet.cmd,packet.ret,packet.uin,packet.seq,packet.eBody.length,packet.error);
        if (packet.ret == RetCodePushMsg) {
            if (self.pushDelegate != nil) {
                [self.pushDelegate onPush:packet];
            }
            [gcd_socket readDataToLength:4 withTimeout:READ_TIME_OUT tag:TAG_LENGTH_HEADER];
        }else if (packet.ret == RetCodeErrorNoSession) {
            //
            DLog(@"kick out");
            [gcd_socket disconnect];
            [[UserLogic shareInstance] logout];
        } else {
            NSString *key = [NSString stringWithFormat:@"%d", packet.seq];
            Request *req = [sendedOutReqsDic objectForKey:key];
            if (req != nil) {
                
                if (req.delegate && [req.delegate respondsToSelector:@selector(onResult:req:)])
                {
                    [sendedOutReqsDic removeObjectForKey:key];
                    [req.delegate onResult:packet req:req];
                }
            }
            [gcd_socket readDataToLength:4 withTimeout:READ_TIME_OUT tag:TAG_LENGTH_HEADER];
        }
    }
    
}



//写入data完成会触发
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
}


- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag
                 elapsed:(NSTimeInterval)elapsed
               bytesDone:(NSUInteger)length {
    return 0;
}


- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag
                 elapsed:(NSTimeInterval)elapsed
               bytesDone:(NSUInteger)length {
    return 0;
}


@end
